#pragma once
#include "Database.h"
#include <vector>
#include <map>

namespace ese
{
	class column;
	class table
	{
	public:

		typedef std::vector<uint8_t> bookmark;

		table() = delete;
		table(const table&) = delete;
		table(table&&) = delete;

		table(ese::database &db) : m_jtid(JET_tableidNil)
			, m_db(db)
			, m_last_error(JET_errSuccess) {}
		table(database& db, const JET_TABLEID tid) : m_jtid(tid)
			, m_db(db)
			, m_last_error(JET_errSuccess) {}

		~table() { try { close(); } catch (...) {} }

		const JET_TABLEID id(void) const { return m_jtid; }
		const JET_ERR last_error(void) const { return m_last_error; }
		database& database() { return m_db; }
		const std::string name() const { return m_name; }

		bool close();
		bool open(std::string tableName, const JET_GRBIT grbit = 0);

		bool create(std::string tableName, const int pagesCount = 1, const int density = 0);
		bool create_index(const char* name, IndexKey& key, const JET_GRBIT grbit = 0, unsigned long density = 50);
		bool create_index(const char* name, const char *key, const JET_GRBIT grbit = 0, unsigned long density = 50);
		
		bool set_index(const char* name);

		// columns
		const size_t column_count() const { return m_columns.size(); }
		ese::column& column(int ix){ return *m_columns[ix].get(); }
		ese::column& column(const std::string& name){ return *m_columns[m_columns_by_name[name]].get(); }

		bool detect_columns();
		bool create_column(const char *name, const JET_COLTYP columnType, const JET_GRBIT grbit = JET_bitColumnFixed, const unsigned long maxSize = 0);

		bool prepare(const unsigned long prepareOption);
		bool prepare_insert() { return prepare(JET_prepInsert); }
		bool prepare_replace() { return prepare(JET_prepReplace); }
		bool update(bookmark& bm);

	private:
		ese::database& m_db;
		JET_TABLEID m_jtid;
		JET_ERR m_last_error;
		std::string m_name;

		std::vector< std::shared_ptr< ese::column > > m_columns;
		std::map< std::string, int > m_columns_by_name;

	};

	inline bool table::close()
	{
		if (JET_tableidNil == m_jtid)
		{
			return true;
		}// if

		m_last_error = ::JetCloseTable(m_db.session().id(), m_jtid);
		m_jtid = JET_tableidNil;
		m_name = "";
		return (m_last_error == JET_errSuccess);
	}

	inline bool table::create(std::string tableName, const int pagesCount, const int density)
	{
		int Density = ((density < 20) || (density > 100)) ? 0 : density;
		if (JET_errSuccess == (m_last_error = ::JetCreateTableA(m_db.session().id(), m_db.id(), tableName.c_str(), pagesCount, Density, &m_jtid)))
		{
			m_name = tableName;
			return true;
		}// if
		return false;
	}

	inline bool table::create_index(const char* name, IndexKey &key, const JET_GRBIT grbit, unsigned long density)
	{
		return (JET_errSuccess == (m_last_error = JetCreateIndex(m_db.session().id(), id()
			, name, grbit, key.data(), static_cast<unsigned long>(key.size()), density)));
	}
	inline bool table::create_index(const char* name, const char *ckey, const JET_GRBIT grbit, unsigned long density)
	{
		return create_index(name, IndexKey(ckey), grbit, density);
	}

	inline bool table::set_index(const char* name)
	{
		return (JET_errSuccess == (m_last_error = JetSetCurrentIndex(m_db.session().id(), id(), name)));
	}

	inline bool table::open(std::string tableName, const JET_GRBIT grbit)
	{
		if (JET_errSuccess == (m_last_error = ::JetOpenTableA(m_db.session().id(), m_db.id(), tableName.c_str(), nullptr, 0, grbit, &m_jtid)))
		{
			m_name = tableName;
			return detect_columns();
		} // if

		return false;
	}

	inline bool table::prepare(const unsigned long prepareOption)
	{
		return (JET_errSuccess == (m_last_error = JetPrepareUpdate(m_db.session().id(), id(), prepareOption)));
	}

	inline bool table::update(bookmark& bk)
	{
		if (bk.empty())
		{
			bk.resize(4);
		}
		unsigned long size = static_cast<unsigned long>(bk.size());
		JET_ERR jErr = JetUpdate(m_db.session().id(), id(), &bk[0], size, &size);
		if (jErr == JET_errBufferTooSmall)
		{
			bk.resize(size);
			size = static_cast<unsigned long>(bk.size());
			jErr = JetUpdate(m_db.session().id(), id(), &bk[0], size, &size);
			bk.resize(size);
		}// if

		return ((m_last_error = jErr) == JET_errSuccess);
	}

}
